Blog:
Iniciando com OpenCV nos processadores i.MX 6
Como diz o ditado, uma imagem vale mais que mil palavras. De fato, isso é verdade até certo ponto. Uma imagem pode conter informações sobre objetos, textos, pessoas, idades, situações, dentre outros. Isso também pode ser estendido à videos, que podem ser interpretados como uma série de imagens.
Esta pode ser uma boa dica de porquê a visão computacional (CV, de Computer Vision) tem sido um campo de estudo que tem seus horizontes sendo expandidos todos os dias. Então nos vem a questão: O que é visão computacional? Ela é a habilidade de extrair informações de uma imagem, ou uma série de imagens. Ela não deve ser confundida com aquisição de imagens digitais nem com processamento de imagens, que são respectivamente a produção de uma imagem de entrada e uma aplicação de operações matemáticas para imagens. Entretanto, ambas são necessárias para tornar a CV possível.
Embora algumas tarefas sejam triviais para seres-humanos, como ler ou reconhecer pessoas, isso nem sempre é verdade quando estamos falando sobre computadores interpretando imagens. Entretanto, nos dias de hoje existem diversas aplicações que já são bem conhecidas e utilizadas no dia a dia, como por exemplo detecção de faces em câmeras digitais, detecção de caracteres ópticos em scanners de livros (OCR) e leitura de placas dos veículos no trânsito. Estes são campos que não estavam nem perto de existir há 15 anos. Carros autônomos que vão de ambientes controladores para as ruas são uma boa medida do quão avançada está essa tecnologia, e uma das ferramentas que torna CV possível é o avanço do poder computacional em hardwares menores.
Assim, este blog é uma introdução de como utilizar a visão computacional em sistemas embarcados, utilizando as versões 2.4 e 3.1 do OpenCV em computadores em módulos (CoMs) equipados com o processador NXP i.MX 6. Os CoMs que escolhemos neste artigo foram das famílias Colibri e Apalis da Toradex.
OpenCV significa Open Source Computer Vision Library, um conjunto de bibliotecas que contém centenas de algoritmos relacionados a visão computacional. O OpenCV tem uma estrutura modular, dividida em uma biblioteca principal (core) e várias outras como um módulo de processamento de imagens, um módulo de análise de vídeo, um módulo de interface com o usuário, entre outros.
O OpenCV é um conjunto de bibliotecas que computa operações matemáticas na CPU por padrão. Ele tem suporte para processamento multicore ao usar algumas bibliotecas externas como o OpenMP (Open Multi-processing) e o TBB (Threading Building Blocks). Este blog não irá se aprofundar na comparação entre as bibliotecas disponíveis, mas o desempenho de algumas aplicações provavelmente irá diferir entre testes com diferentes bibliotecas.
Falando um pouco sobre o suporte ao coprocessador de ponto flutuante NEON, a versão 3.0 do OpenCV menciona que aproximadamente 40 funções foram aceleradas e um novo HAL (de Hardware Abstraction Layer ou Camada de Abstração de Hardware) provê um fácil meio para criar um código otimizado com NEON, o que é um bom caminho para aprimorar o desempenho na maioria dos sistemas embarcados Arm. Neste artigo não iremos nos aprofundar neste assunto, mas caso você tenha interesse em ir mais fundo nisso, uma dica é dar uma olhada no código fonte do OpenCV (1, 2).
Esse post apresentará como usar o OpenCV 2.4 e também o OpenCV 3.1 - isso foi decidido porque pode haver usuários com aplicações legadas que necessitam da versão mais antiga. É, também, uma boa oportunidade para comparar o desempenho entre as versões e ter uma noção sobre os ganhos de otimização com NEON.
O SoC i.MX 6 Solo/DualLite tem uma GPU gráfica (GC880) com suporte a OpenGL ES, enquanto o SoC i.MX6 Dual/Quad, tem uma GPU gráfica 3D (GC2000) que suporta OpenGL ES e também OpenCL Embedded Profile, mas não o Full Profile. O i.MX 6 também tem uma GPU 2D (GC320), uma IPU, e para a versão Dual/Quad, uma GPU de vetores (GC335), mas neste blog nós não iremos discutir sobre a possibilidade de utilizar esses hardwares com o OpenCV – é suficiente dizer que o código fonte do OpenCV não os suporta por padrão. Assim, seria necessária uma quantidade considerável de trabalho para aproveitar os hardwares específicos do i.MX 6.
Enquanto o OpenCL é uma linguagem de programação de propósitos gerais para GPU, seu uso não é o objetivo deste blog. O OpenGL é uma API para renderização de gráficos 2D e 3D na GPU, e portanto, não tem como objetivo principal ser utilizado para propósitos gerais de computação. Entretanto, alguns experimentos (1) demonstraram que é possível utilizar o OpenGL ES para propósitos gerais em processamento de imagens, existindo até uma nota de aplicação feita pela NXP para aqueles que se interessarem. Se você tem interesse em usar a aceleração de GPU no OpenCV out-of-the-box, a Toradex tem um módulo que suporta CUDA – o Apalis TK1. Veja este blog post para mais detalhes.
Apesar de todas os recursos de hardware disponíveis e possivelmente utilizáveis para obter desempenho, de acordo com esta apresentação, a otimização do código-fonte do OpenCV, focando apenas em software e no coprocessador NEON, poderia proporcionar uma melhoria de desempenho em 2-3x para o algoritmo e, com outras otimizações com NEON, poderia alcançar uma melhoria de 3-4x.
As imagens 1 e 2 apresentam, respectivamente, o módulo Colibri iMX6DL + Colibri Evaluation Board e o Apalis iMX6Q + Apalis Evaluation Board, ambos contendo os cabos da fonte, UART de debug, ethernet, câmera USB e monitor VGA conectados.
É importante mencionar que diferentes modelos de câmera USB podem apresentar diferentes desempenhos, e a câmera utilizada neste blog post é uma câmera genérica que encontramos – o driver foi listado como “Aveo Technology Corp”. No mercado também existem câmeras profissionais, USB ou não, como por exemplo as soluções providenciadas pela Basler AG, que tem como objetivo serem utilizadas em soluções embarcadas quando se é necessário desenvolver uma solução no mundo real.
Ainda, a Toradex disponibiliza o CSI Camera Module 5MP OV5640. É um periférico feito para a família Apalis que utiliza a interface MIPI-CSI. Ela contém o sensor de câmera OmniVision OV5640 com auto foco embutido. O OV5640 é um sensor de imagem CMOS de 1/4 polegadas e 5 megapixels de baixa tensão e alto desempenho que provê a funcionalidade total de uma única câmera de 5 megapixels (2592x1944). O CSI Camera Module 5MP OV5640 pode ser conectado ao conector MIPI-CSI presente na Ixora Carrier Board V1,1 utilizando um cabo FFC de 24 vias com passo de 0.5mm.
No final deste blog post, é fornecido um resumo das instruções de como instalar o OpenCV e como implementar aplicações no target.
Imagens com o OpenCV podem ser criadas utilizando a ferramenta OpenEmbedded. Todas as instruções que iremos executar a seguir estão neste artigo. O primeiro passo é instalar os requisitos para o OpenEmbedded. Abaixo é dado um exemplo para o Ubuntu 16.04 – para outras versões do Ubuntu ou Fedora, por favor consulte ao artigo mencionado anteriormente:
sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install g++-5-multilib sudo apt-get install curl dosfstools gawk g++-multilib gcc-multilib lib32z1-dev libcrypto++9v5:i386 libcrypto++-dev:i386 liblzo2-dev:i386 libstdc++-5-dev:i386 libusb-1.0-0:i386 libusb-1.0-0-dev:i386 uuid-dev:i386 cd /usr/lib; sudo ln -s libcrypto++.so.9.0.0 libcryptopp.so.6
Também é necessário instalar a ferramenta repo para buscar os diversos repositórios utilizados para construir a imagem:
mkdir ~/bin export PATH=~/bin:$PATH curl http://commondatastorage.googleapis.com/git-repo-downloads/repo > ~/bin/repo chmod a+x ~/bin/repo
Vamos realizar o build da imagens com o OpenCV 2.4 e o 3.1 em diretórios diferentes. Alguns passos podem ser omitidos caso você tenha interesse em apenas uma versão. Também será criado um diretório para compartilhar o conteúdo baixado pelo OpenEmbedded.
cd mkdir oe-core-opencv2.4 oe-core-opencv3.1 oe-core-downloads cd oe-core-opencv2.4 repo init -u http://git.toradex.com/toradex-bsp-platform.git -b LinuxImageV2.6.1 repo sync cd ../oe-core-opencv3.1 repo init -u http://git.toradex.com/toradex-bsp-platform.git -b LinuxImageV2.7 repo sync
O OpenCV 2.4 será incluído na versão de imagem 2.6.1, pule essa seção caso queira utilizar a versão 3.1 do OpenCV.
A receita incluída por padrão usa a versão 2.4.11 que não tem suporte ao processamento multicore por padrão.
Remova o append presente na meta-fsl-arm e o append presente na meta-toradex-demos:
rm layers/meta-fsl-arm/openembedded-layer/recipes-support/opencv/opencv_2.4.bbappend rm layers/meta-toradex-demos/recipes-support/opencv/opencv_2.4.bbappend
Vamos criar um append para utilizar a versão 2.4.13.2 e adicionar o TBB para termos a vantagem dos múltiplos núcleos.
Acesse o diretório oe-core-opencv2.4:
cd oe-core-opencv2.4
A receita deve ser criada na camada meta-toradex-demos (layers/meta-toradex-demos/recipes-support/opencv/opencv_2.4.bbappend) com o seguinte conteúdo:
gedit layers/meta-toradex-demos/recipes-support/opencv/opencv_2.4.bbappend ------------------------------------------------------------------------------------- SRCREV = "d7504ecaed716172806d932f91b65e2ef9bc9990" SRC_URI = "git://github.com/opencv/opencv.git;branch=2.4" PV = "2.4.13.2+git${SRCPV}" PACKAGECONFIG += " tbb" PACKAGECONFIG[tbb] = "-DWITH_TBB=ON,-DWITH_TBB=OFF,tbb,"
Alternativamente, podemos também utilizar o OpenMP no lugar do TBB:
gedit layers/meta-toradex-demos/recipes-support/opencv/opencv_2.4.bbappend ------------------------------------------------------------------------------------- SRCREV = "d7504ecaed716172806d932f91b65e2ef9bc9990" SRC_URI = "git://github.com/opencv/opencv.git;branch=2.4" PV = "2.4.13.2+git${SRCPV}" EXTRA_OECMAKE += " -DWITH_OPENMP=ON"
Configure o ambiente em seu computador ao executar o comando abaixo dentro do diretório oe-core-opencv2.4:
. export
Você automaticamente irá entrar em um diretório chamado build, o próximo passo é editar o arquivo conf/local.conf adicionando as variáveis abaixo:
gedit conf/local.conf ------------------------------------------------------------------------------- MACHINE ?= "apalis-imx6" # or colibri-imx6 depending on the CoM you have # Use the previously created folder for shared downloads, e.g. DL_DIR ?= "/home/user/oe-core-downloads" ACCEPT_FSL_EULA = "1" # libgomp is optional if you use TBB IMAGE_INSTALL_append = " opencv opencv-samples libgomp"
Agora, você pode realizar o primeiro build da imagem. Esse processo irá demorar algumas horas. Execute o comando abaixo:
bitbake -k angstrom-lxde-image
O OpenCV 3.1 será incluído na versão de imagem 2.7.
Nesta receita o TBB já vem incluído por padrão, entretanto, uma flag de compilação deve ser adicionada para que o processo não falhe. Acesse o diretório oe-core-opencv3.1:
cd oe-core-opencv3.1
Vamos criar um append adicionando essa flag na camada meta-openembedded (layers/meta-openembedded/meta-oe/recipes-support/opencv/opencv_3.1.bb) com o seguinte conteúdo:
gedit layers/meta-toradex-demos/recipes-support/opencv/opencv_3.1.bbappend ------------------------------------------------------------------------------------- CXXFLAGS += " -Wa,-mimplicit-it=thumb"
Alternativamente, podemos também utilizar o OpenMP no lugar do TBB:
gedit layers/meta-toradex-demos/recipes-support/opencv/opencv_3.1.bbappend ------------------------------------------------------------------------------------- CXXFLAGS_armv7a += " -Wa,-mimplicit-it=thumb" PACKAGECONFIG_remove = "tbb" EXTRA_OECMAKE += " -DWITH_OPENMP=ON"
Configure o ambiente em seu computador ao executar o comando abaixo dentro do diretório oe-core-opencv3.1:
. export
Você automaticamente irá entrar em um diretório chamado build. O próximo passo é editar o arquivo conf/local.conf adicionando as variáveis abaixo:
gedit conf/local.conf ------------------------------------------------------------------------------- MACHINE ?= "apalis-imx6" # or colibri-imx6 depending on the CoM you have # Use the previously created folder for shared downloads, e.g. DL_DIR ?= "/home/user/oe-core-downloads" ACCEPT_FSL_EULA = "1" # libgomp is optional if you use TBB IMAGE_INSTALL_append = " opencv libgomp"
Agora, você pode realizar o primeiro build da imagem. Esse processo irá demorar algumas horas. Para isso, execute o comando abaixo:
bitbake -k angstrom-lxde-image
Após o término do build, você poderá encontrar os arquivos gerados no diretório oe-core-opencv<versão>/deploy/images/<nome_do_módulo> com o nome <nome_do_módulo>_LXDE-Image-Tezi_2.8b6-<data>.tar. Siga as instruções presentes neste artigo para atualizar seu módulo.
Gerando a SDK
Para gerar a SDK que será utilizada para cross-compilar as aplicações, execute o seguinte comando:
bitbake -c populate_sdk angstrom-lxde-image
Após o término do processo, você irá encontrar a SDK no diretório /deploy/sdk, execute o script para instalar e mantenha o caminho de instalação padrão:
./angstrom-glibc-x86_64-armv7at2hf-vfp-neon-v2015.12-toolchain.sh Angstrom SDK installer version nodistro.0 ========================================= Enter target directory for SDK (default: /usr/local/oecore-x86_64):
Nos próximos passos, serão assumidos os seguintes diretórios para as SDKs:
Para OpenCV 2.4: /usr/local/oecore-opencv2_4
Para OpenCV 3.1: /usr/local/oecore-opencv3_1
Tendo a SDK instalada já é possível utilizá-la para compilar nossa aplicação. Para isso, vamos utilizar alguns Makefiles, portanto será necessário instalar o CMake:
sudo apt-get install cmake
Crie um arquivo chamado CMakeLists.txt dentro da pasta do seu projeto com o conteúdo abaixo. Tenha certeza de que o caminho para a toolchain esteja correto e de que o nome do sysroot dentro da pasta da SDK é o mesmo que no script (e.g. armv7at2hf-neon-angstrom-linux-gnueabi)
cd ~ mkdir my_project gedit CMakeLists.txt -------------------------------------------------------------------------------- cmake_minimum_required(VERSION 2.8) project( MyProject ) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") add_executable( myApp src/myApp.cpp ) if(OCVV EQUAL 2_4) message(STATUS "OpenCV version required: ${OCVV}") SET(CMAKE_PREFIX_PATH /usr/local/oecore-opencv${OCVV}/sysroots/armv7at2hf-vfp-neon-angstrom-linux-gnueabi) elseif(OCVV EQUAL 3_1) message(STATUS "OpenCV version required: ${OCVV}") SET(CMAKE_PREFIX_PATH /usr/local/oecore-opencv${OCVV}/sysroots/armv7at2hf-neon-angstrom-linux-gnueabi) else() message(FATAL_ERROR "OpenCV version needs to be passed. Make sure it matches your SDK version. Use -DOCVV=<version>, currently supported 2_4 and 3_1. E.g. -DOCVV=3_1") endif() SET(OpenCV_DIR ${CMAKE_PREFIX_PATH}/usr/lib/cmake/OpenCV) find_package( OpenCV REQUIRED ) include_directories( ${OPENCV_INCLUDE_DIRS} ) target_link_libraries( myApp ${OPENCV_LIBRARIES} )
Também é necessário ter um arquivo CMake para dizer quais as bibliotecas serão utilizadas. Para isso, iremos criar arquivos CMake dentro do diretório sysroot de cada SDK. Vamos primeiro criar para a versão 2.4
cd /usr/local/oecore-opencv2_4/sysroots/armv7at2hf-vfp-neon-angstrom-linux-gnueabi/usr/lib/cmake mkdir OpenCV gedit OpenCV/OpenCVConfig.cmake ----------------------------------------------------------------------------------- set(OPENCV_FOUND TRUE) get_filename_component(_opencv_rootdir ${CMAKE_CURRENT_LIST_DIR}/../../../ ABSOLUTE) set(OPENCV_VERSION_MAJOR 2) set(OPENCV_VERSION_MINOR 4) set(OPENCV_VERSION 2.4) set(OPENCV_VERSION_STRING "2.4") set(OPENCV_INCLUDE_DIR ${_opencv_rootdir}/include) set(OPENCV_LIBRARY_DIR ${_opencv_rootdir}/lib) set(OPENCV_LIBRARY -L${OPENCV_LIBRARY_DIR} -lopencv_calib3d -lopencv_contrib -lopencv_core -lopencv_features2d -lopencv_flann -lopencv_gpu -lopencv_highgui -lopencv_imgproc -lopencv_legacy -lopencv_ml -lopencv_nonfree -lopencv_objdetect -lopencv_ocl -lopencv_photo -lopencv_stitching -lopencv_superres -lopencv_video -lopencv_videostab) if(OPENCV_FOUND) set( OPENCV_LIBRARIES ${OPENCV_LIBRARY} ) set( OPENCV_INCLUDE_DIRS ${OPENCV_INCLUDE_DIR} ) endif() mark_as_advanced(OPENCV_INCLUDE_DIRS OPENCV_LIBRARIES)
O mesmo deve ser feito para a versão 3.1. Note que as bibliotecas mudam da versão 2 para a 3:
cd /usr/local/oecore-opencv3_1/sysroots/armv7at2hf-vfp-neon-angstrom-linux-gnueabi/usr/lib/cmake mkdir OpenCV gedit OpenCV/OpenCVConfig.cmake ----------------------------------------------------------------------------------- set(OPENCV_FOUND TRUE) get_filename_component(_opencv_rootdir ${CMAKE_CURRENT_LIST_DIR}/../../../ ABSOLUTE) set(OPENCV_VERSION_MAJOR 3) set(OPENCV_VERSION_MINOR 1) set(OPENCV_VERSION 3.1) set(OPENCV_VERSION_STRING "3.1") set(OPENCV_INCLUDE_DIR ${_opencv_rootdir}/include) set(OPENCV_LIBRARY_DIR ${_opencv_rootdir}/lib) set(OPENCV_LIBRARY -L${OPENCV_LIBRARY_DIR} -lopencv_aruco -lopencv_bgsegm -lopencv_bioinspired -lopencv_calib3d -lopencv_ccalib -lopencv_core -lopencv_datasets -lopencv_dnn -lopencv_dpm -lopencv_face -lopencv_features2d -lopencv_flann -lopencv_fuzzy -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_line_descriptor -lopencv_ml -lopencv_objdetect -lopencv_optflow -lopencv_photo -lopencv_plot -lopencv_reg -lopencv_rgbd -lopencv_saliency -lopencv_shape -lopencv_stereo -lopencv_stitching -lopencv_structured_light -lopencv_superres -lopencv_surface_matching -lopencv_text -lopencv_tracking -lopencv_videoio -lopencv_video -lopencv_videostab -lopencv_xfeatures2d -lopencv_ximgproc -lopencv_xobjdetect -lopencv_xphoto) if(OPENCV_FOUND) set( OPENCV_LIBRARIES ${OPENCV_LIBRARY} ) set( OPENCV_INCLUDE_DIRS ${OPENCV_INCLUDE_DIR} ) endif() mark_as_advanced(OPENCV_INCLUDE_DIRS OPENCV_LIBRARIES)
Após isso, retorne à pasta do projeto e exporte as variáveis de ambiente e rode o script do CMake:
# For OpenCV 2.4 source /usr/local/oecore-opencv2_4/environment-setup-armv7at2hf-vfp-neon-angstrom-linux-gnueabi cmake -DOCVV=2_4 . # For OpenCV 3.1 source /usr/local/oecore-opencv3_1/environment-setup-armv7at2hf-neon-angstrom-linux-gnueabi cmake -DOCVV=3_1 .
Para testar o ambiente de cross-compilação, iremos compilar uma aplicação de “Olá Mundo” que pegará uma imagem de um arquivo e mostrará na nossa tela. O código é o mesmo encontrado neste tutorial de OpenCV:
mkdir src gedit src/myApp.cpp --------------------------------------------------------------------------------- #include <iostream>#include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main(int argc, char** argv ){ cout << "OpenCV version: " << CV_MAJOR_VERSION << '.' << CV_MINOR_VERSION << "\n"; if ( argc != 2 ){ cout << "usage: ./myApp \n"; return -1; } Mat image; image = imread( argv[1], 1 ); if ( !image.data ){ cout << "No image data \n"; return -1; } bitwise_not(image, image); namedWindow("Display Image", WINDOW_AUTOSIZE ); imshow("Display Image", image); waitKey(0); return 0; }
Para compilar e passar o binário gerado para a placa, siga as instruções abaixo. A placa deve ter acesso à LAN - ligando um cabo Ethernet ou através de um adaptador WiFi-USB, por exemplo. Para descobrir o IP da placa, use o comando ifconfig.
make scp bin/myApp root@<board-IP>/home/root # Also copy some image to test! scp <path-to-image>/myimage.jpg root@<board-IP>:/home/root
Na sua placa, rode a aplicação.
root@colibri-imx6:~# ./myApp
Você deverá ver a imagem na tela, como na Imagem 3:
Nesta seção, testaremos um algoritmo que lê a entrada da câmera, processando-a usando a detecção de borda inteligente. Este é um dos diversos algoritmos já implementados no OpenCV. A propósito, o OpenCV tem uma extensa documentação, com tutoriais, exemplos e bibliotecas de referência.
No Linux, quando uma câmera é conectada, ela pode ser acessada através do sistema de arquivos. O dispositivo normalmente é montado no diretório /dev. Vamos dar uma olhada no conteúdo deste diretório em um Apalis iMX6 antes de conectar uma câmera USB:
root@apalis-imx6:~# ls /dev/video* /dev/video0 /dev/video1 /dev/video16 /dev/video17 /dev/video18 /dev/video19 /dev/video2 /dev/video20
E após plugar:
root@apalis-imx6:~# ls /dev/video* /dev/video0 /dev/video1 /dev/video16 /dev/video17 /dev/video18 /dev/video19 /dev/video2 /dev/video20 /dev/video3
Note que a câmera foi listada como /dev/video3. Isso pode ser confirmado com o utilitário de linha de comando video4linux2 (rode v4l2-ctl –help para mais detalhes):
root@apalis-imx6:~# v4l2-ctl --list-devices [ 3846.876041] ERROR: v4l2 capture: slave not found! V4L2_CID_HUE [ 3846.881940] ERROR: v4l2 capture: slave not found! V4L2_CID_HUE [ 3846.887923] ERROR: v4l2 capture: slave not found! V4L2_CID_HUE DISP4 BG ():[ 3846.897425] ERROR: v4l2 capture: slave not found! V4L2_CID_HUE /dev/video16 /dev/video17 /dev/video18 /dev/video19 /dev/video20 UVC Camera (046d:081b) (usb-ci_hdrc.1-1.1.3): /dev/video3 Failed to open /dev/video0: Resource temporarily unavailable
Além disso, também é possível obter os parâmetros do dispositivo. Note que a webcam usada nos testes tem uma resolução de 640x480 pixels.
root@apalis-imx6:~# v4l2-ctl -V --device=/dev/video3 Format Video Capture: Width/Height : 640/480 Pixel Format : 'YUYV' Field : None Bytes per Line: 1280 Size Image : 614400 Colorspace : SRGB
Ainda, uma vez que todas as interfaces de vídeo são abstraídas pelo kernel Linux, se você optasse por usar o CSI Camera Module 5MP OV5640 da Toradex, ele também seria listado como outra interface de vídeo. No Apalis iMX6, os drivers do kernel são carregados por padrão.
Agora vamos ao código. O objeto do OpenCV que faz o manuseio da entrada da câmera, VideoCapture, aceita o índice da câmera (que é 0 para o nosso exemplo de /dev/video0) ou -1 para detectar automaticamente o dispositivo de câmera.
Um loop infinito processa o vídeo quadro-a-quadro. O nosso exemplo aplica filtros de conversão para a escala de cinza → desfoque glaussiano → detecção de borda Canny e então envia a imagem processada para a saída de vídeo. O código pode ser visto abaixo:
#include <iostream>#include <cstdlib> #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> using namespace std; using namespace cv; int main(int argc, char** argv ){ cout << "OpenCV version: " << CV_MAJOR_VERSION << '.' << CV_MINOR_VERSION << '\n'; VideoCapture cap(-1); // searches for video device if(!cap.isOpened()){ cout << "Video device could not be opened\n"; return -1; } Mat edges; namedWindow("edges",1); double t_ini, fps; for(; ; ){ t_ini = (double)getTickCount(); // Image processing Mat frame; cap >> frame; // get a new frame from camera cvtColor(frame, edges, COLOR_BGR2GRAY); GaussianBlur(edges, edges, Size(7,7), 1.5, 1.5); Canny(edges, edges, 0, 30, 3); // Display image and calculate current FPS imshow("edges", edges); if(waitKey(30) >= 0) break; fps = getTickFrequency()/((double)getTickCount() - t_ini); cout << "Current fps: " << fps << '\r' << flush; } return 0; }
A fotografia abaixo apresenta o resultado. Observe que os parâmetros dos algoritmos Glaussian e Canny não foram ajustados.
Para compararmos os valores entre o Colibri iMX6S e o iMX6DL e nos certificarmos que o processamento multicore estava sendo utilizado, uma média de 1000 amostras foram medidas e os resultados estão presentes na tabela 1. O mesmo foi feito para o Apalis iMX6 com um heatsink embutido. Todos os testes foram feitos com a escalonamento de frequência desabilitado.
FPS médio (1000 amostras) | Colibri iMX6S 256MB IT | Colibri iMX6DL 512MB | Apalis iMX6Q 1GB |
OpenCV 3.1 (TBB) | 8.84 | 11.85 | 12.16 |
OpenCV 3.1 (OpenMP) | 8.67 | 10.04 | 10.10 |
OpenCV 2.4 (TBB) | 7.35 | 8.82 | 8.67 |
OpenCV 2.4 (OpenMP) | 7.42 | 8.23 | 8.72 |
Como comparação, o algoritmo Canny foi substituido no código pelas derivadas de Sobel, baseado neste exemplo. Os resultados são mostrados na tabela 2:
FPS médio (1000 amostras) | Colibri iMX6S 256MB IT | Colibri iMX6DL 512MB | Apalis iMX6Q 1GB |
OpenCV 3.1 (TBB) | 9.62 | 11.09 | 11.31 |
OpenCV 3.1 (OpenMP) | 9.57 | 11.23 | 11.00 |
OpenCV 2.4 (TBB) | 7.04 | 8.33 | 8.23 |
OpenCV 2.4 (OpenMP) | 7.03 | 7.80 | 8.28 |
É interessante notar que o TBB foi mais rápido que o OpenMP na maioria dos casos. Também é interessante notar que a diferença é pequena até nos testes de um único núcleo.
Além disso, as melhorias feitas do OpenCV 2.4 para o OpenCV 3.1 tiveram um impacto significativo no desempenho da aplicação - o que pode ser explicado pelas otimização com NEON. Baseado nos testes preliminares, é interessante realizar um teste em uma aplicação específica com diversas combinações de otimizações e também utilizar apenas a última versão do OpenCV.
Também foi testado um exemplo de detecção de rostos cujo código vem com o OpenCV, já que é um teste mais pesado que os anteriores e pode demonstrar melhor a diferença de desempenho entre Colibri e Apalis na hora de decidir o hardware mais apropriado para o seu projeto.
O primeiro passo foi copiar o código fonte da aplicação do GitHub e substituir o conteúdo do arquivo myApp.cpp, ou criar um novo arquivo e modificar o script CMake. Também será necessário o Haar Cascade disponibilizado para reconhecimento de face e de olhos. Uma sugestão para quem quer se aprofundar mais é clonar o repositório do OpenCV e testar os outros exemplos.
Algumas pequenas modificações devem ser feitas para que o código fonte funcione também na versão 2.4 do OpenCV: incluir a biblioteca stdio.h, modificar as bibliotecas incluídas do OpenCV (dê uma olhada nesta seção para mais informações) e por último realizar algumas modificações no CommandLineParser, já que as versões 2 e 3 do OpenCV não são compatíveis. A imagem 5 apresenta a aplicação sendo executada:
O código já apresenta o tempo necessário para executar a detecção de face. A tabela 3 abaixo apresenta os resultados para 100 amostras:
FPS médio (1000 amostras) | Colibri iMX6S 256MB IT | Colibri iMX6DL 512MB | Apalis iMX6Q 1GB |
OpenCV 3.1 (TBB) | 800.72 | 342.02 | 199.97 |
OpenCV 3.1 (OpenMP) | 804.33 | 357.56 | 189.32 |
OpenCV 2.4 (TBB) | 2671.00 | 1081.10 | 637.46 |
OpenCV 2.4 (OpenMP) | 2701.00 | 1415.00 | 623.44 |
Novamente a diferença entre versões do OpenCV é significante – para o Apalis iMX6Q (quatro núcleos) o ganho de desempenho foi de 3x, e o OpenCV 3.1 com o Colibri iMX6DL entregou um desempenho melhor que o OpenCV 2.4 com o Apalis iMX6Q.
O Colibri iMX6S de temperatura industrial, que tem uma frequência de clock reduzida em comparação aos outros, apresentou um desempenho muito abaixo do que o esperado. O clock reduzido pode explicar por que a melhora de desempenho do módulo com um único núcleo para o de dois núcleos foi maior de que o de dois núcleos para quatro núcleos
Como o OpenCV é provavelmente o conjunto de bibliotecas mais popular para visão computacional e com a melhoria na performance dos computadores embarcados, saber como começar com o OpenCV para aplicativos embarcados fornece uma ferramenta poderosa para resolver problemas dos dias de hoje e do futuro próximo.
Este blog post escolheu uma família amplamente conhecida de microprocessadores como exemplo e ponto de partida, seja para aqueles que estudam apenas a visão computacional ou para aqueles que já se concentram no desenvolvimento de aplicativos do mundo real. Também demonstramos que as coisas estão apenas começando - veja, por exemplo, as melhorias de desempenho do OpenCV 2 para 3 para os processadores Arm - que também apontam para a necessidade de entender a arquitetura do sistema e se concentrar em otimizar o aplicativo o máximo possível - mesmo sob o capô. Espero que este blog post tenha sido útil e até a próxima!
1) Construindo imagem com OpenCV
Para criar uma imagem com o OpenCV 3.1, primeiro siga as etapas deste artigo para configurar o OpenEmbedded para a imagem Toradex V2.7. Edite a receita OpenCV do meta-openembedded e adicione a seguinte linha:
CXXFLAGS += " -Wa,-mimplicit-it=thumb"
No arquivo local.conf, descomente sua MACHINE, aceite a licença da Freescale e adicione o OpenCV:
MACHINE ?= "apalis-imx6" # or colibri-imx6 depending on the CoM you have ACCEPT_FSL_EULA = "1" IMAGE_INSTALL_append = " opencv"
2) Gerando SDK
Assim você já poderá rodar a build e também gerar o SDK:
bitbake -k angstrom-lxde-image bitbake -c populate_sdk angstrom-lxde-image
Para gravar a imagem no sistema embarcado, siga os passos deste artigo.
3) Preparando para compilação cruzada
Após instalar a SDK, haverá um sysroot para o target (Arm) dentro do diretório do SDK. Crie um diretório OpenCV dentro de
cd/usr/lib/cmake mkdir OpenCV vim OpenCVConfig.cmake --------------------------------------------------------------------------- set(OPENCV_FOUND TRUE) get_filename_component(_opencv_rootdir ${CMAKE_CURRENT_LIST_DIR}/../../../ ABSOLUTE) set(OPENCV_VERSION_MAJOR 3) set(OPENCV_VERSION_MINOR 1) set(OPENCV_VERSION 3.1) set(OPENCV_VERSION_STRING "3.1") set(OPENCV_INCLUDE_DIR ${_opencv_rootdir}/include) set(OPENCV_LIBRARY_DIR ${_opencv_rootdir}/lib) set(OPENCV_LIBRARY -L${OPENCV_LIBRARY_DIR} -lopencv_aruco -lopencv_bgsegm -lopencv_bioinspired -lopencv_calib3d -lopencv_ccalib -lopencv_core -lopencv_datasets -lopencv_dnn -lopencv_dpm -lopencv_face -lopencv_features2d -lopencv_flann -lopencv_fuzzy -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_line_descriptor -lopencv_ml -lopencv_objdetect -lopencv_optflow -lopencv_photo -lopencv_plot -lopencv_reg -lopencv_rgbd -lopencv_saliency -lopencv_shape -lopencv_stereo -lopencv_stitching -lopencv_structured_light -lopencv_superres -lopencv_surface_matching -lopencv_text -lopencv_tracking -lopencv_videoio -lopencv_video -lopencv_videostab -lopencv_xfeatures2d -lopencv_ximgproc -lopencv_xobjdetect -lopencv_xphoto) if(OPENCV_FOUND) set( OPENCV_LIBRARIES ${OPENCV_LIBRARY} ) set( OPENCV_INCLUDE_DIRS ${OPENCV_INCLUDE_DIR} ) endif() mark_as_advanced(OPENCV_INCLUDE_DIRS OPENCV_LIBRARIES)
Dentro da pasta do seu projeto, crie um script do CMake para gerar os Makefiles para a compilação cruzada com o seguinte conteúdo:
cdvim CMakeLists.txt -------------------------------------------------------------------------------- cmake_minimum_required(VERSION 2.8) project( MyProject ) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") add_executable( myApp src/myApp.cpp ) SET(CMAKE_PREFIX_PATH /usr/local/oecore-x86_64/sysroots/armv7at2hf-neon-angstrom-linux-gnueabi) SET(OpenCV_DIR ${CMAKE_PREFIX_PATH}/usr/lib/cmake/OpenCV) find_package( OpenCV REQUIRED ) include_directories( ${OPENCV_INCLUDE_DIRS} ) target_link_libraries( myApp ${OPENCV_LIBRARIES} )
Exporte as variáveis de ambiente do SDK e então rode o script do CMake. Para fazer isso, você pode executar:
source /usr/local/oecore-x86_64/environment-setup-armv7at2hf-neon-angstrom-linux-gnueabi cmake .
4) Compilação cruzada e deploy
Agora você pode criar um diretório src e um arquivo chamado myApp.cpp dentro dele, onde você escreverá uma aplicação "Hello World" como a seguinte:
mkdir src vim src/myApp.cpp -------------------------------------------------------------------------------- #include <iostream>#include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main(int argc, char** argv ){ Mat image; image = imread( "/home/root/myimage.jpg", 1 ); if ( !image.data ){ cout << "No image data \n"; return -1; } namedWindow("Display Image", WINDOW_AUTOSIZE ); imshow("Display Image", image); waitKey(0); return 0; }
Cross compile e faça o deploy para o target:
make
scp bin/myApp root@<board-ip>:/home/root
# Also copy some image to test!
scp <path-to-image>/myimage.jpg root@<board-ip>:/home/root
No seu sistema embarcado:
root@colibri-imx6:~# ./myApp